#include "connectthread.h"
#include "socketcontext.h"
#include "contextmanager.h"
#ifdef Linux
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/types.h>
#else
    #include <winsock2.h>
#endif
#include "contextmanager.h"
#include "ioworkerthreadpool.h"
#include "notifyobject.h"

queue<shared_ptr<SocketContext>> ConnectThread::que;

namespace  {

    template<Socket_Io_Operation op, typename Mem, typename... Args>
    void _bind(Mem &&mem, Args... args)
    {
        mem->sop(op);
        NOTIFYOBJECT->_bind<op>(forward<Mem>(mem), args...);
    }
    bool signalhandlethread(const shared_ptr<SocketContext> &sock, const shared_ptr<MemPool>& mem)
    {
        return sock->pushmempool(forward<const shared_ptr<MemPool> &>(mem)) ?  (DISPATCHER->dispatch(sock), true) : false;
    }
    void freesockcontext(const shared_ptr<SocketContext> &sock)
    {
        sock->close();
        _CM_MANAGER_(SocketContext)->push(sock);
    }

    bool connectproc(const shared_ptr<SocketContext> &sock)
    {
        bool berr = false;
        std::int32_t err = -1;
    #ifdef Linux
        std::uint32_t len = sizeof(std::uint32_t);
    #else
        std::int32_t len = sizeof(std::int32_t);
    #endif
        sockaddr_in		remotesock_addr, localsock_addr;
        if (getsockopt(sock->sockid(), SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&err), &len) == -1) {
            freesockcontext(sock);
            return berr;
        }
        len = sizeof(sockaddr);
        if(getpeername(sock->sockid(), reinterpret_cast<sockaddr *>(&remotesock_addr), &len) == -1)   {
            freesockcontext(sock);
            return berr;
        }
        len = sizeof(sockaddr);
        if(getsockname(sock->sockid(), reinterpret_cast<sockaddr *>(&localsock_addr), &len) == -1)    {
            freesockcontext(sock);
            return berr;
        }
        if(closenagle(sock->sockid()) == false || opennonblock(sock->sockid()) == false)  {
            freesockcontext(sock);
            return berr;
        }

        sock->afterconstruct(localsock_addr.sin_addr.s_addr, localsock_addr.sin_port, remotesock_addr.sin_addr.s_addr, remotesock_addr.sin_port);

        _IW_MANAGER_->init(sock);
        berr = true;

        return berr;
    }

}
void ConnectThread::run()
{
    const chrono::seconds sec(5);
    auto pred = [this]() {
        return bnotify;
    };

    while(!bquit)
    {
        {
            unique_lock<mutex> lck(mtx);
            bool bret =  cv.wait_for(lck, sec, pred);
            if(bquit)   break;
            if(bret == false)      continue;
        }
        handproc();
    }
}

pair<shared_ptr<SocketContext>, bool> ConnectThread::pop()
{
    shared_ptr<SocketContext> sock;
    unique_lock<mutex> lck(lmtx);
    bnotify = false;
    sock = que.front();
    que.pop();
    return make_pair(sock, que.empty());
}

void ConnectThread::handproc()
{
    pair<shared_ptr<SocketContext>, bool> it;
    do  {
        it = pop();
        if(it.first)    {
            if(it.first->connect() == -1) {
                shared_ptr<MemPool> mem = it.first->popmempool();
                _bind<Socket_Io_Operation::TcpSocketIoConnectFail>(forward<shared_ptr<MemPool> &>(mem),
                                                                   it.first->remoteip(), it.first->us_remoteport(), &(it.first->param()));
                signalhandlethread(it.first, mem);
            }
            else       connectproc(it.first);
        }
    }while(!it.second);

}

void ConnectThread::init()
{
    connectthread = thread(&ConnectThread::run, this);
}

void ConnectThread::wait()
{
    connectthread.join();
}

void ConnectThread::quit()
{
    bquit = true;
    unique_lock<mutex> lck(mtx);
    cv.notify_one();
    bnotify = true;
}

void ConnectThread::uninit()
{
    unique_lock<mutex> lck(mtx);
    while(!que.empty()){
        _CM_MANAGER_(SocketContext)->push(que.front());
        que.pop();
    }
}

void ConnectThread::push(const shared_ptr<SocketContext> &sock)
{
    unique_lock<mutex> lck(mtx);
    que.push(sock);
    if(que.size() == 1)    {
        cv.notify_one();
    }
    bnotify = true;
}

shared_ptr<ConnectThread> &ConnectThread::instance()
{
    static shared_ptr<ConnectThread> sptr = make_shared<ConnectThread>();
    return sptr;
}

